/**
 * 权限标准定义了公共的基本组件用于管理其他需要浏览器授权的API。同时也定义了API用于查询和请求变更指定权限
 * 权限API标准规格还处于工作草案阶段，并不稳定，因此各大平台支持也不完善，兼容性较差，我们主要以了解为主
 */

/**
 *    这篇规格为在 web 平台中使用强力特性定义了权限模型和一套API用于查询这些强力特向的权限。
 * 当前，这些强力的API是自己在管理权限。比如，Notification API 允许开发者去请求权限并明确的检查权限状态。
 * 一些API，比如 geolocation API 会在调用时在页面上弹出权限弹窗。query 方法为开发者在权限提示显示时提供了控制工具。
 * 必须要指出的是，本规格描述的权限模型虽然是可扩展的，但并不适合全部现在和未来的webAPI。
 */

/**
 * 权限请求描述符：
 *    PermissionDescriptor {
 *        name: {PermissionName} 必要
 *    }
 *    每一个强力特性都有一个或多个方面可以被权限请求访问。为了描述这些方面，每个特性都定义了对应的 PermissionDescriptor 子类型作为权限描述符
 * 比如 midi 是否允许访问系统级信息，这一特性的权限描述符如下：
 *    MidiPermissionDescriptor : PermissionDescriptor {
 *        boolean sysex = false 用于描述是否访问系统级信息 
 *    }
 * bulutooth 是否允许访问所有靠近的用户设备，这一特性权限描述符如下：
 *    BluetoothPermissionDescriptor : PermissionDescriptor {
 *        string deviceId
 *        Array[BluetoothLEScanFilterInit] filter
 *        Array[BluetoothServiceUUID] optionalServices = []
 *        boolean acceptAllDevices = false
 *    }
 */

/**
 * 权限状态
 *    UA 会追踪每个强力特性是否用使用权。其他的特性API可以使用本规格定义的操作来检索UA权限是允许还是拒绝，
 * 并且可以请求用户允许或者拒绝更多的权限。其他API规格还可以通过算法，添加额外的约束行为到UA上。
 * 本节中，描述符是descriptor.name对应的强力特性的权限描述类型。
 *    PermissionStatus 接口实例 {
 *        state: {PermissionState} 只读， 当前权限状态
 *        onchange: {Handler} 权限状态变化事件
 *    }
 *    PermissionState {
 *        'granted': 表示调用者可以成功访问特性，而不用UA申请用户权限
 *        'denied': 表示不允许调用者访问特性
 *        'prompt': UA 正在申请用户权限，此时用户必须给出同意或者拒绝
 *    }
 */

/**
 * navigator 和 WorkerNavigator 扩展
 *    [Exposed=(Window)]
 *    Navigator {
 *        permissions: {Permissions} 只读属性
 *    }
 *    [Exposed=(Worker)]
 *    WorkerNavigator {
 *        permissions: {Permissions} 只读属性
 *    }
 */

/**
 * Permissions 接口实例 {
 *    query: {Promise<PermissionStatus> Function(object permissionDesc)} 根据给入的描述符查询权限状态
 * }
 */

/**
 * 权限注册
 *    注册可查询权限的API名 枚举类型 PermissionName {
 *      "geolocation",
 *      "notifications",
 *      "push",
 *      "midi",
 *      "camera",
 *      "microphone",
 *      "speaker",
 *      "device-info",
 *      "background-sync",
 *      "bluetooth",
 *      "persistent-storage",
 *      "ambient-light-sensor",
 *      "accelerometer",
 *      "gyroscope",
 *      "magnetometer",
 *      "clipboard",
 *    }
 *    上面每一个枚举值都定义了一个强力特性，每个强力特性都有下面的权限关系标志、算法和类型
 * 非安全上下文允许标志：
 *    默认情况下，只有安全上下文（https）可以使用强力特性。如果某个特性的这个标志被设置，UA也许允许在非安全上下文中访问该特性。
 * 
 * 权限描述类型：
 *    是 PermissionDescriptor 或者子类型之一，如果为指定，默认为 PermissionDescriptor。
 * 这个特性会在描述符实例中增加一个局部顺序，如果描述符A先于描述符B，则描述符A的权限状态如果为“granted”那么描述符B也必须“granted”，
 * 同样，如果描述符B的权限状态为“denied“那么描述符A必须为”denied“
 * 
 * 可选权限状态约束：
 *    UA返回的描述符的权限状态值上的约束，默认情况下是没有超出用于意图的。
 * 
 * 可选的额外权限数据类型：
 *    如果指定，那么这个特性的额外权限数据算法启用
 * 
 * 可选的额外权限数据约束：
 *    UA返回的一个权限名的额外权限数据值上的约束，默认情况下不会超过用户意图
 * 
 * 权限结果类型：
 *    是 permissionStatus 或者是子类型之一，如果为指定，默认为 permissionStatus
 * 
 * 权限查询算法：
 *    使用权限描述类型实例和新的或者已存在权限结果类型实例作为参数查询，并以查询结果更新权限结果类型实例。
 * 这个算法被 Permissions 的 query 方法和 权限状态更新的步骤使用。如果未指定，默认为布尔权限查询算法。
 * 
 * 权限取消算法：
 *    无需参数。更新那些需要与权限状态或者额外权限数据变更保持一致的实现的其他部分。如果未指定，则不做任何事情
 */

/**
 * 下面是一个具体API的权限描述
 * Geolocation:
 *    geolocation 权限在 Geolocation API 中使用，是 boolean 特性，允许在非安全上下文中访问
 * 
 * Notifications:
 *    notifications 权限在 Notifications API 中使用，是 boolean 特性，允许在非安全上下文中访问
 * 
 * Push：
 *    push 权限在 Push API 中使用，权限描述符如下：
 *    PushPermissionDescriptor : PermissionDescriptor {
 *        boolean  userVisibileOnly = false;
 *    }
 *    { name: 'push', userVisibileOnly: false } 比 { name: 'push', userVisibileOnly: true } 优先
 * 
 * Midi：
 *    midi 权限在 webmidi 中使用。允许在非安全上下文中访问，权限描述符如下：
 *    MidiPermissionDescriptor : PermissionDescriptor {
 *        sysex = false;   
 *    }
 *    {name: "midi", sysex: true} 比 {name: "midi", sysex: false} 优先
 * 
 * Media Devices：
 *    “camear"、"microphone"和"speaker"权限在 getUserMedia 方法和 audio-output 规格中使用。
 * “speaker"权限允许在非安全上下文中访问，”camear“和”microphone“可能会允许在非安全上下文中访问。
 * 另外，如果当前全局对象下有关联的 document 对象，且 document 对象下属性 allowusermedia 为 false，
 * 即不允许使用媒体特性，那么，“camear”和“microphone”权限必须为“denied”。
 *    权限描述符为
 *    DevicePermissionDescriptor : PermissionDescriptor {
 *        DOMString deviceId
 *    }
 * 权限覆盖了访问描述符中给出的所有设备。如果描述符中没有给出 devicedId ，意为要访问所有设备。因此，
 * 如果一个“camear”权限查询没有给出 deviceId 但返回了 “granted”，这说明客户端知道不用弹出相机权限提示，
 * 如果返回 “denied”，客户端则知道没有 getUserMedia 成功。如果有权限状态可以访问某些相机而不是全部，那么
 * 不带 deviceId 的查询会返回 ”prompt“。
 *    注意，一个“granted”的权限不保证 getUserMedia 一定会成功。它仅保证用户不会看到权限提示。还有其他原因
 * （比如约束或者相机被占用）会导致 getUserMedia 失败。
 *    额外权限数据类型是一组设备id值，来自于用户进行非默认访问。
 * 
 * Background Sync
 *    "background-sync" 权限在 web-background-sync 规格中使用
 * 
 * Ambient Light Sensor
 *    “ambient-light-sensor” 权限在 ambient-light API 中使用
 *    权限取消算法是通过传感器权限取消算法带参（acceleometer）执行的结果
 * 
 * Accelerometer（加速器）
 *    accelerometer 权限在 accelerometer API 中使用
 *    权限取消算法是通过传感器权限取消算法带参（acceleometer）执行的结果
 * 
 * Magnetometer（磁力计）
 *    “magnetometer” 权限在 magnetometer API 中使用
 *    权限取消算法是通过传感器权限取消算法带参（acceleometer）执行的结果
 * 
 * Clipboard
 *    “clipboard” 权限在 clipboard API 中使用
 */

// 下面是一些本API的用法实例
/**
 * 此示例使用权限API来决定是使用地理位置API显示本地新闻，还是使用按钮提供添加功能
 */
const example1 = () => {
  navigator.permissions.query({
    name: 'geolocation'
  }).then(state => {
    console.log(state); // PermissionState
    switch (state.state) {
      case 'granted': console.log('showLocalNewsWithGeolocation'); break;
      case 'prompt': console.log('showButtonToEnableLocalNews'); break;
      default: break;
    }
  })
}
example1();

/**
 * 下面的例子使用“notifications”权限，根据权限状态为聊天应用实现一个提示按钮
 */
const example2 = () => {
  const updateNotificationButton = (state = '') => {
    document.getElementById('chat-notification-button')
      .disabled = (state === 'denied');
  }

  navigator.permissions.query({
    name: 'notifications'
  }).then(res => {
    console.log(res);
    updateNotificationButton(res.state);
    res.addEventListener('change', () => {
      updateNotificationButton(res.state);
    })
  })
}
example2();

/**
 * 下面的例子使用Promise.all 检查了页面是否允许了 "geolocation" 和 “notifications”
 */
const example3 = () => {
  Promise.all([
    navigator.permissions.query({ name: 'geolocation' }),
    navigator.permissions.query({ name: 'notifications' })
  ]).then(([ {state: geoState}, {state:notifState} ]) => {
    console.log(`Geolocation permission state is: ${geoState}`)
    console.log(`Notifications permission state is: ${notifState}`)
  })
}
example3();

/**
 * 下面的例子检查可用摄像头的授权状态
 */
const example4 = () => {
  navigator.mediaDevices.enumerateDevices()
    .then(devices => {
      console.log(devices);
      return Promise.all(devices
        .filter(({ kind }) => kind === 'videoinput')
        .map(({ deviceId }) => ({ name: 'camear', deviceId }))
        .map(queryObj => navigator.permissions.query(queryObj))
      )
    })
    .then(results => results.forEach(({ state }, i) => console.log(`Camear ${i}: "${state}"`)))
    .catch(e => console.error(e))
}
example4();